package com.google.code.expr.runtime;

import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Properties;

import com.google.code.expr.lang.EXPRAtom;
import com.google.code.expr.lang.EXPRAtomReader;
import com.google.code.expr.lang.EXPRBlockComponent;
import com.google.code.expr.lang.EXPRConditionalCase;
import com.google.code.expr.lang.EXPRConditonalType;
import com.google.code.expr.lang.EXPREcho;
import com.google.code.expr.lang.EXPRParseException;
import com.google.code.expr.lang.EXPRSet;
import com.google.code.expr.lang.EXPRStream;

public class EXPRRunTime {
	
	private EXPRRunTimeState mState = new EXPRRunTimeState();
	
	public EXPRRunTime() {
		super();
	}

	public void setInitialState(Properties pProperties) {
		for (Object key : pProperties.keySet()) {
			String varName = key.toString();
			mState.setVariableFromString(varName, pProperties.getProperty(varName));
		}
	}
	
	
	public void run(EXPRAtomReader pReader, OutputStream pOut) throws EXPRRunTimeException, EXPRParseException {
		run(pReader, new OutputStreamWriter(pOut));
	}
	public void run(EXPRAtomReader pReader, Writer pOut) throws EXPRRunTimeException, EXPRParseException {
		try {
			EXPRConditionalCase cond = executeBlock(pReader, new BufferedWriter(pOut),true);
			if (cond!=null) {
				throw new EXPRParseException(cond, "Encountered "+cond.getType()+" before a "+EXPRConditonalType.IF);
			}
		}
		catch (EXPRRunTimeException e) {
			throw e;
		}
		catch (EXPRParseException e) {
			throw e;
		}
		catch (IOException e) {
			throw new EXPRRunTimeException(e.getMessage(),e);
		}
	}
	
	protected EXPRConditionalCase executeBlock(EXPRAtomReader pReader, BufferedWriter pOut, boolean pOutputting) 
	throws IOException, EXPRParseException, EXPRRunTimeException {
		EXPRAtom nextAtom = pReader.read();
		while (nextAtom!=null) {
			if (nextAtom instanceof EXPRConditionalCase) {
				EXPRConditionalCase cond = (EXPRConditionalCase)nextAtom;
				if (cond.getType().equals(EXPRConditonalType.IF)) {
					executeConditionals(cond,pReader,pOut,pOutputting);
				}
				else {
					//if we are in a recursive call, this will return last atom to
					//executeConditionals for it to continue
					return cond;
				}
			}
			else if (nextAtom instanceof EXPRBlockComponent) {
				if (pOutputting) {
					EXPRBlockComponent pComponent = (EXPRBlockComponent)nextAtom;
					if (pComponent instanceof EXPRStream) {
						executeStream((EXPRStream)pComponent,pOut);
					}
					else if (pComponent instanceof EXPREcho) {
						executeEcho((EXPREcho)pComponent,pOut);
					}
					else if (pComponent instanceof EXPRSet) {
						executeSet((EXPRSet)pComponent,pOut);
					}
					pOut.flush();
				}
			}
			else {
				throw new EXPRParseException(nextAtom, "Encountered unexpected type "+nextAtom.getClass().getName());
			}
			nextAtom = pReader.read();
		}
		return null;
	}
	
	protected void executeConditionals(EXPRConditionalCase pIf, EXPRAtomReader pReader, BufferedWriter pOut, boolean pOutputting) 
	throws IOException, EXPRParseException, EXPRRunTimeException {
		boolean foundCase = false;
		boolean encountedElse = false;
		EXPRConditionalCase nextCond = null;
		
		//if case
		EXPRValue result = pIf.getExpression().resolveToValue(mState);
		if (result.isBoolean() && result.getBoolean().booleanValue()) {
			foundCase = true;
			nextCond = executeBlock(pReader,pOut,pOutputting);
		}
		else {
			nextCond = executeBlock(pReader,pOut,false);
		}
		while (nextCond!=null && !(nextCond.getType().equals(EXPRConditonalType.ENDIF))) {
			if (nextCond.getType().equals(EXPRConditonalType.ELSE)) {
				if (encountedElse) {
					throw new EXPRParseException(nextCond, "Encountered a second "+EXPRConditonalType.ELSE);
				}
				if (!foundCase) {
					nextCond = executeBlock(pReader,pOut,pOutputting);
				}
				else {
					nextCond = executeBlock(pReader,pOut,false);
				}
				encountedElse = true;
				foundCase = true;
			}
			else if (nextCond.getType().equals(EXPRConditonalType.ELSEIF)) {
				if (encountedElse) {
					throw new EXPRParseException(nextCond, "Encountered an "+EXPRConditonalType.ELSEIF+" after an "+EXPRConditonalType.ELSE);
				}
				result = nextCond.getExpression().resolveToValue(mState);
				if (!foundCase && result.isBoolean() && result.getBoolean().booleanValue()) {
					foundCase = true;
					nextCond = executeBlock(pReader,pOut,pOutputting);
				}
				else {
					nextCond = executeBlock(pReader,pOut,false);
				}
			}
			else {
				throw new EXPRParseException(nextCond, "Unexpected conditinal "+nextCond.getType()+" found.");
			}
		}
	}

	protected void executeStream(EXPRStream pStream, BufferedWriter pOut) throws IOException,EXPRRunTimeException {
		pOut.write(pStream.getContent());
	}

	protected void executeEcho(EXPREcho pEcho, BufferedWriter pOut) throws IOException,EXPRRunTimeException {
		pOut.write(pEcho.getOperand().resolveToString(mState));
	}
	
	protected void executeSet(EXPRSet pSet, BufferedWriter pOut) throws IOException,EXPRRunTimeException {
		EXPRVariable var = mState.getVariable(pSet.getVariable().getName());
		if (var==null) {
			var = new EXPRVariable(pSet.getVariable().getName());
			mState.setVariable(var.getName(), var);
		}
		var.setValue(pSet.getOperand().resolveToValue(mState));
	}
	
	public static void main(String[] pArgs) throws Exception {
		FileReader fis = new FileReader("test/simple.expr");
		EXPRAtomReader reader = new EXPRAtomReader(fis);
		reader.setTrimStream(false);

		EXPRRunTime rt = new EXPRRunTime();
		Properties state = new Properties();
		state.load(new FileInputStream("test/simple.properties"));
		rt.setInitialState(state);
		rt.run(reader, System.out);
	}
}
